package com.hmdp.utils;

import com.hmdp.service.IFollowService;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * 雪花算法生成全局唯一id
 */
@Component
public class SnowFlakeUtil {
    //64位id：符号位1位 时间戳41位 机器id5位 服务id5位 序列号12位

    // 初始时间戳：雪花算法服务上线时间戳的值 2022-04-04 16:08:08
    private static final long INIT_EPOCH = LocalDateTime.of(2022,4,4,16,8,8)
                                                        .atZone(ZoneId.of("UTC"))
                                                        .toInstant()
                                                        .toEpochMilli();
    //同一毫秒内序列号，最新为 0
    private long sequence;
    //序列号sequence占 12位
    private static final long SEQUENCE_BITS = 12L;
    //sequence最大为 111111111111
    private static final long MAX_SEQUENCE = ~(-1l << SEQUENCE_BITS);

    //workerId 服务id
    private long workerId;
    //workerId占用5个比特位
    private static final long WORKER_ID_BITS = 5L;
    //workId最大值为 111111
    private static final long MAX_WORKER_ID = ~(-1l << WORKER_ID_BITS);
    //workId需要左移 12位
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;

    //dataCenterId 机器id
    private long dataCenterId;
    //dataCenterId占用5个比特位
    private static final long DATA_CENTER_ID_BITS = 5L;
    //dataCenterId最大值为 11111
    private static final long MAX_DATA_CENTER_ID = ~(-1l << DATA_CENTER_ID_BITS);
    //dataCenterId需要左移 12+5位
    private static final long DATA_CENTER_ID_SHIFT = WORKER_ID_BITS+SEQUENCE_BITS;

    //时间戳需要左移 12+5+5位
    private static final long TIMESTAMP_SHIFT = DATA_CENTER_ID_SHIFT+WORKER_ID_SHIFT+SEQUENCE_BITS;

    //最后一次使用的毫秒时间戳
    private long lastTimeMillis = -1L;

    /**
     * 无参构造
     */
    public SnowFlakeUtil() {
        this(1,1);
    }

    /**
     * 有参构造
     * @param dataCenterId
     * @param workerId
     */
    public SnowFlakeUtil(long dataCenterId, long workerId) {
        //校验机器id和服务id的合法性
        if(dataCenterId > MAX_DATA_CENTER_ID||dataCenterId < 0) {
            throw new IllegalArgumentException("dataCenterId不合法");
        }
        if(workerId > MAX_WORKER_ID||workerId < 0) {
            throw new IllegalArgumentException("workerId不合法");
        }

        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    /**
     * 生成下一个id
     * @return
     */
    public synchronized Long nextId() {
        long currentTimeMillis = System.currentTimeMillis();
        if(currentTimeMillis < lastTimeMillis) {
            String.format("可能出现服务器时钟回拨问题，请检查服务器时间。当前服务器时间戳：%d，" +
                            "上一次使用时间戳：%d", currentTimeMillis,lastTimeMillis);
        }
        if(currentTimeMillis == lastTimeMillis) {
            //同一毫秒内，时间戳不变，序列号+1
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if(sequence == 0) {
               //序列号超出最大值，使用新的时间戳
               currentTimeMillis = getNextMillis(lastTimeMillis);
            }
        }
        else {
            //不在同一毫秒内，到达新的时间，序列号重新从0开始
            sequence = 0;
        }

        //更新lastTimeMillis
        lastTimeMillis = currentTimeMillis;

        //构造id:各部分各自移位，由于左移后 后面补0，与其他部分进行按位或运算
        return (currentTimeMillis - INIT_EPOCH) << TIMESTAMP_SHIFT
                | (dataCenterId<<DATA_CENTER_ID_SHIFT)
                | (workerId<<WORKER_ID_SHIFT)
                | sequence;
    }

    /**
     * 获取下一毫秒时间戳,保证不会因为服务器时钟问题生成重复id
     * @param lastTimeMills
     * @return
     */
    private long getNextMillis(long lastTimeMills) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimeMills) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}
